﻿using System;
using System.Collections.Generic;
using Microsoft.VisualStudio.Text.Tagging;
using Microsoft.VisualStudio.Text;
using Microsoft.RestrictedUsage.CSharp.Compiler.IDE;
using Microsoft.RestrictedUsage.CSharp.Syntax;
using Microsoft.RestrictedUsage.CSharp.Core;
using Microsoft.RestrictedUsage.CSharp.Extensions;
using System.Linq;
using System.Diagnostics;

namespace Marias.Extensions.CSharp
{
    internal sealed class StructureTagger : ITagger<IOutliningRegionTag>
    {

        IDECompilerHost _CompHost;
        FileName _FileName;
        IDECompiler _compiler;
        bool _IsValid = false;

        public StructureTagger(ITextBuffer p_buffer, string p_filename)
        {
            _FileName = new FileName(p_filename);
            _CompHost = new IDECompilerHost();
            foreach (IDECompiler i_comp in _CompHost.Compilers)
            {
                if (i_comp.SourceFiles.Keys.Contains(_FileName))
                {
                    _compiler = i_comp;
                    _IsValid = true;
                }
            }
        }

        public IEnumerable<ITagSpan<IOutliningRegionTag>> GetTags(NormalizedSnapshotSpanCollection p_spans)
        {
            if (p_spans.Count == 0 || !_IsValid)
                yield break;
            Stopwatch i_sp = Stopwatch.StartNew();
            ParseTree i_tree;
            LexData i_LexData;
            try
            {
                i_tree = _compiler.SourceFiles[_FileName].GetParseTree();
                i_LexData = i_tree.LexData;

            } catch (Exception ex)
            {
                Debug.WriteLine(ex.Message);
                yield break;
            }

            List<TagSpan<IOutliningRegionTag>> i_toprocess = new List<TagSpan<IOutliningRegionTag>>();
            int i_start = p_spans.First().Start.Position;
            int i_end = p_spans.Last().End.Position;
            if (i_end <= i_start)
                yield break;
            LookForAllBreakable(i_tree.RootNode, i_toprocess, p_spans[0].Snapshot, i_start, i_end);
            foreach (TagSpan<IOutliningRegionTag> s in i_toprocess)
            {
                yield return s;
            }
            i_sp.Stop();
            Debug.WriteLine("Last outlining: " + i_sp.ElapsedMilliseconds.ToString("N0"));
            i_sp.Reset();
        }


        private TagSpan<IOutliningRegionTag> ProcessIfBlock(ParseTreeNode p_node, ITextSnapshot p_ss)
        {
            if (p_node.Children.Count() == 2 && p_node.Children.Last().AsBlock() == null)
                return null; //simple If with one expression

            LexData i_ld = p_node.ParseTree.LexData;
            ParseTreeNode i_StartNode = p_node.Children.Skip(1).First();

            int initpos = PositionFromCSharpPosition(i_ld.GetTokenAt(i_StartNode.Token).StartPosition, p_ss);
            int startpos = PositionFromCSharpPosition(i_ld.GetTokenAt(p_node.Children.First().TokenExtent.Last).EndPosition, p_ss) + 1;
            int endpos = PositionFromCSharpPosition(i_ld.GetTokenAt(p_node.TokenExtent.Last).EndPosition, p_ss);
            try
            {
                SnapshotSpan s = new SnapshotSpan(p_ss, startpos, endpos - startpos);
                return new TagSpan<IOutliningRegionTag>(s,
                new OutliningRegionTag(false, false, "..." + p_node.Kind.ToString() + " block...", p_ss.GetText(initpos, endpos - initpos).Trim()));

            } catch (Exception exc)
            {
                return null;
            }
        }

        private TagSpan<IOutliningRegionTag> ProcessSimpleBlock(ParseTreeNode p_node, ITextSnapshot p_ss)
        {
            ParseTreeNode block = GetInnerBlockNode(p_node);
            if (block != null)
            {
                LexData i_ld = block.ParseTree.LexData;


                int initpos = PositionFromCSharpPosition(i_ld.GetTokenAt(block.Token).StartPosition, p_ss);
                int startpos = PositionFromCSharpPosition(i_ld.GetTokenAt(block.Token - 1).EndPosition, p_ss); //LookForPrevWordsEnd(initpos, p_ss);
                int endpos = PositionFromCSharpPosition(i_ld.GetTokenAt(block.TokenExtent.Last).EndPosition, p_ss);
                SnapshotSpan s = new SnapshotSpan(p_ss, startpos, endpos - startpos);

                try
                {
                    return new TagSpan<IOutliningRegionTag>(s,
                       new OutliningRegionTag(false, false, "..." + p_node.Kind.ToString() + " block...", p_ss.GetText(initpos, endpos - initpos).Trim()));

                } catch (Exception exc)
                {
                    return null;
                }

            } else
                return null;
        }

        private TagSpan<IOutliningRegionTag> ProcessSwitchBlock(ParseTreeNode p_node, ITextSnapshot p_ss)
        {
            ParseTreeNode expr = p_node.Children.First();
            if (expr != null)
            {
                LexData i_ld = p_node.ParseTree.LexData;

                int initpos = PositionFromCSharpPosition(i_ld.GetTokenAt(expr.TokenExtent.Last).EndPosition, p_ss) + 1; //after the expression
                int endpos = PositionFromCSharpPosition(i_ld.GetTokenAt(p_node.TokenExtent.Last).EndPosition, p_ss);
                SnapshotSpan s = new SnapshotSpan(p_ss, initpos, endpos - initpos);
                try
                {
                    return new TagSpan<IOutliningRegionTag>(s,
                   new OutliningRegionTag(false, false, "..." + p_node.Kind.ToString() + " block...", p_ss.GetText(initpos, endpos - initpos).Trim()));
                } catch (Exception exc)
                {
                    return null;
                }


            } else
                return null;
        }


        private void LookForAllBreakable(ParseTreeNode p_node, List<TagSpan<IOutliningRegionTag>> p_result, ITextSnapshot p_ss, int p_start, int p_end)
        {
            if (p_result == null)
                p_result = new List<TagSpan<IOutliningRegionTag>>();

            if (IsInsideTheRegion(p_ss, p_node, p_start, p_end))
            {
                if (p_node.AsIf() != null)
                {
                    TagSpan<IOutliningRegionTag> res = ProcessIfBlock(p_node, p_ss);
                    if (res != null)
                        p_result.Add(res);
                } else if (p_node.AsFor() != null || p_node.AsForeach() != null || p_node.InGroup(NodeGroup.Breakable))
                {
                    if (p_node.AsSwitch() != null)
                    {
                        TagSpan<IOutliningRegionTag> res = ProcessSwitchBlock(p_node, p_ss);
                        if (res != null)
                            p_result.Add(res);
                    } else
                    {
                        TagSpan<IOutliningRegionTag> res = ProcessSimpleBlock(p_node, p_ss);
                        if (res != null)
                            p_result.Add(res);
                    }
                }
            }
            foreach (ParseTreeNode p in p_node.Children)
                if (IsContainerType(p))
                    LookForAllBreakable(p, p_result, p_ss, p_start, p_end);
        }

        private BlockNode GetInnerBlockNode(ParseTreeNode p_node)
        {
            foreach (ParseTreeNode inner in p_node.Children)
            {
                if (inner.AsBlock() != null)
                    return (BlockNode)inner;
            }
            return null;
        }


        private int PositionFromCSharpPosition(Position p_pos, ITextSnapshot p_ss)
        {
            return PositionFromCoordinates(p_pos.Line, p_pos.Character, p_ss);
        }

        private int PositionFromCoordinates(int p_linenr, int p_colnr, ITextSnapshot p_ss)
        {
            return p_ss.GetLineFromLineNumber(p_linenr).Start.Position + p_colnr;
        }

        private bool IsInsideTheRegion(ITextSnapshot p_ss, ParseTreeNode p_node, int p_start, int p_end)
        {
            if (p_node.Token == -1)
                return false;
            LexData i_ld = p_node.ParseTree.LexData;
            Position sp = i_ld.GetTokenAt(p_node.Token).StartPosition;
            Position ep = i_ld.GetTokenAt(p_node.TokenExtent.Last).EndPosition;
            if (PositionFromCoordinates(sp.Line, sp.Character, p_ss) >= p_start || PositionFromCoordinates(ep.Line, ep.Character, p_ss) <= p_end)
                return true;
            else
                return false;
        }

        private bool IsContainerType(ParseTreeNode p_node)
        {
            Type i_type = p_node.GetType();

            if (i_type == typeof(BlockNode))
                return true;
            else if (i_type == typeof(BinaryExpressionNode))
                return false;
            else if (i_type == typeof(UnaryExpressionNode))
                return false;
            else if (i_type == typeof(MethodDeclarationNode))
                return true;
            else if (i_type == typeof(MethodBaseNode))
                return true;
            else if (i_type == typeof(ClassDeclarationNode))
                return true;
            else if (i_type == typeof(StructDeclarationNode))
                return true;
            else if (i_type == typeof(NamespaceDeclarationNode))
                return true;
            else if (i_type == typeof(IfStatementNode))
                return true;
            else if (i_type == typeof(ForStatementNode))
                return true;
            else if (i_type == typeof(ForeachStatementNode))
                return true;
            else if (i_type == typeof(CatchNode))
                return true;
            else if (i_type == typeof(TryStatementNode))
                return true;
            else if (p_node.IsAnyLoopStatement())
                return true;
            else if (i_type == typeof(SwitchStatementNode))
                return true;
            else if (i_type == typeof(SwitchSectionNode))
                return true;
            else
                return false;
        }

        public event EventHandler<SnapshotSpanEventArgs> TagsChanged;
    }
}
